Vamos a ver cómo implementar en C algunas de las funciones que hemos explicado para árboles ABB, al final se incluye un ejemplo completo para árboles de enteros.
Como estamos trabajando con un árbol binario, sólo necesitamos una estructura para referirnos tanto a cualquiera de los nodos como al árbol completo. Recuerda que cualquier nodo puede ser considerado como la raíz de un árbol.
typedef struct _nodo { int dato; struct _nodo *derecho; struct _nodo *izquierdo; } tipoNodo; typedef tipoNodo *pNodo; typedef tipoNodo *Arbol;
Diseñaremos una función que se ajuste al algoritmo que describimos en el punto 7.4:
void Insertar(Arbol *a, int dat) { pNodo padre = NULL; /* (1) */ pNodo actual = *a; /* (2) */ while(!Vacio(actual) && dat != actual->dato) { /* (3) */ padre = actual; if(dat < actual->dato) actual = actual->izquierdo; /* (3-a) */ else if(dat > actual->dato) actual = actual->derecho; /* (3-b) */ } if(!Vacio(actual)) return; /* (4) */ if(Vacio(padre)) { /* (5) */ *a = (Arbol)malloc(sizeof(tipoNodo)); (*a)->dato = dat; (*a)->izquierdo = (*a)->derecho = NULL; } else if(dat < padre->dato) { /* (6) */ actual = (Arbol)malloc(sizeof(tipoNodo)); padre->izquierdo = actual; actual->dato = dat; actual->izquierdo = actual->derecho = NULL; } else if(dat > padre->dato) { /* (7) */ actual = (Arbol)malloc(sizeof(tipoNodo)); padre->derecho = actual; actual->dato = dat; actual->izquierdo = actual->derecho = NULL; } }
Diseñaremos una función que se ajuste al algoritmo que describimos en el punto 7.5:
void Borrar(Arbol *a, int dat) { pNodo padre = NULL; /* (1) */ pNodo actual; pNodo nodo; int aux; actual = *a; while(!Vacio(actual)) { /* Búsqueda (2) else implícito */ if(dat == actual->dato) { /* (3) */ if(EsHoja(actual)) { /* (3-a) */ if(padre)/* (3-a-i caso else implícito) */ if(padre->derecho == actual) padre->derecho = NULL; /* (3-a-ii) */ else if(padre->izquierdo == actual) padre->izquierdo = NULL; /* (3-a-iii) */ free(actual); /* (3-a-iv) */ actual = NULL; return; } else { /* (3-b) */ /* Buscar nodo */ padre = actual; /* (3-b-i) */ if(actual->derecho) { nodo = actual->derecho; while(nodo->izquierdo) { padre = nodo; nodo = nodo->izquierdo; } } else { nodo = actual->izquierdo; while(nodo->derecho) { padre = nodo; nodo = nodo->derecho; } } /* Intercambio */ aux = actual->dato; /* (3-b-ii) */ actual->dato = nodo->dato; nodo->dato = aux; actual = nodo; } } else { padre = actual; if(dat > actual->dato) actual = actual->derecho; /* (4) */ else if(dat < actual->dato) actual = actual->izquierdo; /* (5) */ } } }
Diseñaremos una función que se ajuste al algoritmo que describimos en el punto 7.3:
int Buscar(Arbol a, int dat) { pNodo actual = a; while(!Vacio(actual)) { if(dat == actual->dato) return TRUE; /* dato encontrado (2) */ else if(dat < actual->dato) actual = actual->izquierdo; /* (3) */ else if(dat > actual->dato) actual = actual->derecho; /* (4) */ } return FALSE; /* No está en árbol (1) */ }
Esta función es fácil de implementar:
int Vacio(Arbol r) { return r==NULL; }
Esta función también es sencilla de implementar:
int EsHoja(pNodo r) { return !r->derecho && !r->izquierdo; }
En el punto 7.7 comentábamos que para contar los nodos podemos recurrir a cualquiera de los tres modos de recorrer el árbol: inorden, preorden o postorden y como acción incrementamos el contador de nodos. Para implementar este algoritmo recurrimos a dos funciones:
int NumeroNodos(Arbol a, int *contador) { *contador = 0; auxContador(a, contador); return *contador; } void auxContador(Arbol nodo, int *c) { (*c)++; /* Acción: incrementar número de nodos. (Preorden) */ if(nodo->izquierdo) auxContador(nodo->izquierdo, c); /* Rama izquierda */ if(nodo->derecho) auxContador(nodo->derecho, c); /* Rama derecha */ }
Es un problema parecido al anterior, pero ahora tenemos que contar la altura, no en número de nodos. Cada vez que lleguemos a un nodo hoja, verificamos si la altura del nodo es la máxima, y si lo es, actualizamos la altura del árbol a ese valor:
int AlturaArbol(Arbol a, int *altura) { *altura = 0; /* (1) */ auxAltura(a, 0, altura); return *altura; } void auxAltura(pNodo nodo, int a, int *altura) { /* (2) Cada vez que llamamos a auxAltura pasamos como parámetro a+1 */ if(nodo->izquierdo) auxAltura(nodo->izquierdo, a+1, altura); /* Rama izquierda */ if(nodo->derecho) auxAltura(nodo->derecho, a+1, altura); /* Rama derecha */ if(EsHoja(nodo) && a > *altura) *altura = a; /* Proceso (Postorden) (3) */ }
Lo que haremos será buscar el elemento del nodo del que queremos averiguar la altura. Cada vez que avancemos un nodo incrementamos la variable que contendrá la altura del nodo.
int Altura(Arbol a, int dat) { int altura = 0; pNodo actual = a; /* (1) */ while(!Vacio(actual)) { if(dat == actual->dato) return altura; /* dato encontrado. (2) */ else { altura++; /* (3) */ if(dat < actual->dato) actual = actual->izquierdo; /* (4) */ else if(dat > actual->dato) actual = actual->derecho; /* (5) */ } } return -1; /* No está en árbol */ }
Todos los recorridos se aplican de forma recursiva. En este ejemplo crearemos tres funciones, una por cada tipo de recorrido, que aplicarán una función al elemento de cada nodo.
La función a aplicar puede ser cualquiera que admita como parámetro un puntero a un entero, y que no tenga valor de retorno.
En Inorden, primero procesamos el subárbol izquierdo, después el nodo actual, y finalmente el subárbol derecho:
void InOrden(Arbol a, void (*func)(int*)) { if(a->izquierdo) InOrden(a->izquierdo, func); /* Subárbol izquierdo */ func(&(a->dato)); /* Aplicar la función al dato del nodo actual */ if(a->derecho) InOrden(a->derecho, func); /* Subárbol derecho */ }
En Preorden, primero procesamos el nodo actual, después el subárbol izquierdo, y finalmente el subárbol derecho:
void PreOrden(Arbol a, void (*func)(int*)) { func(&(a->dato)); /* Aplicar la función al dato del nodo actual */ if(a->izquierdo) PreOrden(a->izquierdo, func); /* Subárbol izquierdo */ if(a->derecho) PreOrden(a->derecho, func); /* Subárbol derecho */ }
En Postorden, primero procesamos el subárbol izquierdo, después el subárbol derecho, y finalmente el nodo actual:
void PostOrden(Arbol a, void (*func)(int*)) { if(a->izquierdo) PostOrden(a->izquierdo, func); /* Subárbol izquierdo */ if(a->derecho) PostOrden(a->derecho, func); /* Subárbol derecho */ func(&(a->dato)); /* Aplicar la función al dato del nodo actual */ }
© Abril de 2002 Salvador Pozo, salvador@conclase.net